/*+***********************************************************************************
 Filename: 03_tinycore_step02\src\core.v
 Description: demo the construction of a simple tiny-core.
              step02: a core can execute ADI and JAL.

 Modification:
   2022.10.13 Creation   H.Zheng
   2022.11.01 add LOAD/Store and RAM port
   2022.11.07 add peripheral bus

Copyright (C) 2022  Zheng Hui (hzheng@gzhu.edu.cn)

License: MulanPSL-2.0

Thanks: some codes are learned from Bob Hu's E203 Humingbird project source. His
        Copyright announcement see the bottom of this file.

***********************************************************************************-*/



module core (
    input wire core_clk,
    input wire reset_n,
    //instruction ROM
    output wire [31:0] ibus_addr,
    output wire ibus_re,  //read enable
    input wire [31:0] instruction,
    //data RAM
    output wire ram_ce, //chip enable
    output wire ram_wre, //write enable
    output wire [8:0] ram_addr, //address bus
    input wire [31:0] ram_data_in,
    output wire [31:0] ram_data_out,
    //peripheral
    output wire peripheral_ce, //chip enable
    output wire peripheral_wre, //write enable
    output wire [8:0] peripheral_addr, //address bus
    input wire [31:0] peripheral_data_in,
    output wire [31:0] peripheral_data_out,
    //
    output wire [31:0] monitor_port
);

    //PC
    reg [31:0] program_counter;
    wire [31:0] pc_next;

//    always @(posedge core_clk or negedge reset_n) begin
    always @(posedge core_clk) begin
        if (~reset_n) begin
            program_counter <= ~(32'b0);
        end
        else begin
            program_counter <= pc_next;
        end
    end

    assign ibus_addr = program_counter>>2;
    assign ibus_re = 1'b1;

    //
    //decoder
    //ADI and J only.

    wire [6:0] opcode = instruction[6:0];
    wire [2:0] funct3 = instruction[14:12];

    wire opcode_OP_IMM = (opcode == 7'b0010011);
    wire opcode_JAL = (opcode == 7'b1101111);

    wire [4:0] i_rd_idx = instruction[11:7];
    wire [4:0] i_rs1_idx = instruction[19:15];
    wire [31:0] i_imm_J = {{12{instruction[31]}},instruction[19:12],instruction[20],instruction[30:21],1'b0};
    wire [31:0] i_imm_I = {{21{instruction[31]}},instruction[30:20]};

    wire inst_ADI = opcode_OP_IMM & (funct3 == 3'b000);
    wire inst_JAL = opcode_JAL;
    wire inst_J = inst_JAL & (i_rd_idx == 5'b0);

    wire [4:0] i_rs2_idx = instruction[24:20]; 
    //
    //add LW and SW (2022.11.01)
    wire opcode_LOAD = (opcode == 7'b0000011);
    wire opcode_STORE = (opcode == 7'b0100011);

    wire inst_LW = opcode_LOAD & (funct3 == 3'b010);
    wire inst_SW = opcode_STORE & (funct3 == 3'b010);

    wire [31:0] i_imm_S = {{21{instruction[31]}},instruction[30:25],instruction[11:7]};
    //addr: 
    wire [31:0] loadstore_addr = (inst_LW) ? rf_rs1data + i_imm_I :
                                 (inst_SW) ? rf_rs1data + i_imm_S : 32'b0;
    //assume data ram locate at address map 0x00000000 - 0x000003ff
    //reserve addr for peripheral at        0x00000400 - 0x000007ff
    //so that we can use the 12bit signed imm offset to address the data
    assign ram_ce = (loadstore_addr[31:10] == 22'b0) & (opcode_LOAD | opcode_STORE);

//    assign ram_wre = inst_SW;
    assign ram_wre = opcode_STORE & (~core_clk);

    assign ram_addr = loadstore_addr[10:2]; //loadstore_addr is in bytes but ram_addr is in words

    //output data from rs2
    assign ram_data_out = rf_rs2data;
    

    //execution of ADI
    //

    //fetch rs1&rs2 value from Register File
    wire[31:0] rf_rs1data;
    wire[31:0] rf_rs2data;
    wire rf_rdwen;
    wire[31:0] rf_rd_data;
    wire[31:0] x16_value;
    
    //register file module
    regfile m_regfile(
      .reset_n(reset_n),
      .i_rf_rs1idx(i_rs1_idx),
      .i_rf_rs2idx(i_rs2_idx),
      .rf_rs1data(rf_rs1data),
      .rf_rs2data(rf_rs2data),
      .i_rf_rdidx(i_rd_idx),
      .i_rf_rdwen(rf_rdwen),
      .i_rf_rd_data(rf_rd_data),
      .i_rf_rd_wr_clk(~core_clk),
      .x16_value(x16_value)
    );

    //ADI operation
    wire [31:0] adi_result = rf_rs1data + i_imm_I;

    //LW operation

    //write back
    //rd source:
    // 1) from ALU result;
    // 2) from ram;
    // 3) from peripheral;  (2022.11.07)

//    assign rf_rd_data = adi_result;
//    assign rf_rdwen = inst_ADI;
//    assign rf_rd_data = (inst_ADI) ? adi_result :
//                          (inst_LW) ? ram_data_in : 32'b0;
    wire [31:0] load_result = (ram_ce & opcode_LOAD) ? ram_data_in :
                                 (peripheral_ce & opcode_LOAD) ? peripheral_data_in : 32'b0;

    assign rf_rd_data = (inst_ADI) ? adi_result :
                          (opcode_LOAD) ? load_result : 32'b0;
    assign rf_rdwen = inst_ADI | opcode_LOAD;

    //Jump
    //
    assign pc_next = inst_JAL ? program_counter + i_imm_J : program_counter + 4;  




    //
    assign monitor_port = {ibus_addr[1:0], x16_value[3:0]};
//    assign monitor_port = x16_value;
//    assign monitor_port = {ram_ce, ram_wre, ram_addr[1:0], ram_data_out[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, ram_data_out[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, x16_value[1:0]};
//    assign monitor_port = {inst_J, inst_SW, inst_ADI, inst_LW, ibus_addr[1:0]};
    //assign monitor_port = {inst_J, inst_ADI, ibus_addr[3:0]};
//    assign monitor_port = {rf_rdwen, rf_rdidx};
//    assign monitor_port = {rf_rdwen, i_imm_I[4:0]};


    //peripheral part
    //the ctrl-bus signal is simular to ram
    //since peripheral I/O registers are mapped to the same memory space with ram

    //assume data ram locate at address map 0x00000000 - 0x000003ff
    //assume addr for peripheral at         0x00000400 - 0x000007ff
    //so that we can use the 12bit signed imm offset to address the data
    assign peripheral_ce = (loadstore_addr[31:11] == 20'b0) & (loadstore_addr[10] == 1'b1) & (opcode_LOAD | opcode_STORE);

    assign peripheral_wre = opcode_STORE & (~core_clk);

    assign peripheral_addr = {1'b0, loadstore_addr[9:2]}; //loadstore_addr is in bytes but peripheral_addr is in words

    //output data from rs2
    assign peripheral_data_out = rf_rs2data;
    


endmodule

